//! Pre-startup OS-a, sredi neke stvari prije nego ides na "visu" razinu

#define _ARH_SPREMNIK_C_
#include "spremnik.h"
#include "prekidi.h"
#include "kontekst.h"

#include <jezgra/razno.h>
#include <jezgra/ispis.h>
#include <jezgra/postavke.h>
#include <jezgra/spremnik.h>

//GDT - sadrzi opisnike segmenata
static gdt_t *gdt;

//task state segment:
//koristi se samo jedan, samo elementi ss0 i esp0 za koristenje pri prekidu
static tss_t *tss;

//adresa za prvi iduci proces
static void *arh_adresa_za_procese;

void arh_inicijalizacija_spremnika ()
{
	//postavi pocetni GDT
	extern char mjesto_za_GDT;
	gdt = (void *) &mjesto_za_GDT;

	gdt[SEG_NULL]		= (gdt_t) GDT_0;
	gdt[SEGJ_INSTR]		= (gdt_t) GDT_INSTRUKCIJE_JEZGRE;
	gdt[SEGJ_PODACI]	= (gdt_t) GDT_PODACI_JEZGRE;
	gdt[SEG_TSS]		= (gdt_t) GDT_TSS;
	gdt[SEG_INSTR]		= (gdt_t) GDT_INSTRUKCIJE_PROGRAMA;
	gdt[SEG_PODACI]		= (gdt_t) GDT_PODACI_PROGRAMA;

	gdtr_t gdtr;
	gdtr.gdt = gdt;
	gdtr.limit = sizeof(gdt_t) * 6 - 1;

	//postavi adresu GDT tablice u registar
	__asm__ ("lgdt	%0\n\t" :: "m" (gdtr) );
		
	//azuriraj segmentne registre za jezgru
	__asm__ __volatile__ (
			"ljmp	%0, $1f	\n"	//azuriraj "cs"
		"1:				\n\t"
			"mov	%1, %%eax	\n\t"
			"mov	%%ax, %%ds	\n\t"
			"mov	%%ax, %%es	\n\t"
			"mov	%%ax, %%fs	\n\t"
			"mov	%%ax, %%gs	\n\t"
			"mov	%%ax, %%ss	\n\t"
			:: "i" (GDT_OPISNIK(SEGJ_INSTR, GDT, RAZINA_JEZGRE)),
			"i" (GDT_OPISNIK(SEGJ_PODACI, GDT, RAZINA_JEZGRE))
			: "memory"
	);

	//postavi pocetni TSS
	extern char mjesto_za_TSS;
	tss = (void *) &mjesto_za_TSS;
	tss->ss0 = GDT_OPISNIK(SEGJ_PODACI, GDT, RAZINA_JEZGRE);

	//azuriraj TSS zapis u GDTu
	arh_azuriraj_opisnik_segmenta(SEG_TSS, tss, sizeof(tss_t) - 1, 0);

	//postavi opisnik TSSa u registar
	__asm__ ("ltrw %w0\n\t"::"r"(GDT_OPISNIK(SEG_TSS, GDT, RAZINA_JEZGRE)));


	extern char pocetak_procesi_segmentiranje;
	arh_adresa_za_procese = &pocetak_procesi_segmentiranje;
	//prvi segment se ne dira, njega se kopira za svaki novi proces (!)
}

//zauzimanje spremnika pri stvaranju novog procesa
void arh_zauzmi_spremnik ( arh_spremnik_t *spremnik, void *_pocetak,
			   uint _velicina, bool u_jezgri, bool stranicenje )
{
	void *pocetak = _pocetak;
	uint velicina = _velicina;

	spremnik->stranicenje = stranicenje;

	if ( !stranicenje )
	{
		if (pocetak == NULL && velicina != 0xffffffff)
			pocetak = arh_adresa_za_procese;
		//inace se mapira cijeli adresni prostor - pocetna adresa je 0

		extern char programi_pocetak, programi_velicina;

		if (velicina == 0)
			velicina = (uint) &programi_velicina +
						NAJ_DRETVI * VEL_STRANICE;

		spremnik->pocetna_adresa = pocetak;
		spremnik->odmak = (uint) pocetak;
		spremnik->velicina = velicina;

		if (pocetak != NULL && velicina != 0xffffffff) { //novi prostor
			arh_adresa_za_procese += velicina; //pocetak za iduci

			//"ucitaj" program (i podatke) u spremnik
			memcpy ( spremnik->pocetna_adresa,
				 (void *) &programi_pocetak,
				 (uint) &programi_velicina );
		}
		//else - zadan je pocetak - ne diraj nista
	}
	else { //stranicenje
		spremnik->pocetna_adresa = (void *) 0;
		spremnik->odmak = 0;
		spremnik->velicina = 0xffffffff;
	}

	if (u_jezgri)
		spremnik->razina = 0;
	else
		spremnik->razina = 3;

	spremnik->op_koda =	GDT_OPISNIK (SEG_INSTR,  GDT, spremnik->razina);
	spremnik->op_podataka =	GDT_OPISNIK (SEG_PODACI, GDT, spremnik->razina);

	spremnik->cr0 = 0x01;
	spremnik->cr3 = 0x0;
}

//pocetni stog za novu dretvu
void arh_pripremi_novu_dretvu ( void *_spremnik,
			        void *a_kontekst, void *l_kontekst,
				void *a_stog, void *l_stog,
			        void *pokreni_dretvu,
			        void *poc_funkc, void *param )
{
	arh_spremnik_t *spremnik = _spremnik;
	arh_kontekst_t *kontekst = a_kontekst;

	kontekst->spremnik = spremnik;

	arh_izgradi_pocetni_stog ( spremnik, a_kontekst, l_kontekst,
				   a_stog, l_stog,
				   pokreni_dretvu, poc_funkc, param );
}

//prije vracanja u dretvu
void arh_pripravi_okolinu_dretvi ( void *_kontekst )
{
	arh_kontekst_t *kontekst = _kontekst;

	//gdje je stog sa kontekstom (za povratak iz prekida)
	arh_pripremi_kontekst(kontekst);

	//tss.esp0 se ne azurira automatski po obnovi konteksta
	//za "korisnicke" dretve, napravi to rucno
	//za jezgrine dretve tss.esp0 se ne koristi jer ostaje u prstenu 0
	//tss->esp0 = (uint32) ( &kontekst->kontekst + 1 );
	tss->esp0 = kontekst->esp + sizeof (kontekst_dretve_t);

	//azuriraj zapis za korisnicke segmente u GDTu
	arh_azuriraj_opisnik_segmenta ( SEG_INSTR,
					kontekst->spremnik->pocetna_adresa,
					kontekst->spremnik->velicina, 
					kontekst->spremnik->razina );

	arh_azuriraj_opisnik_segmenta ( SEG_PODACI,
					kontekst->spremnik->pocetna_adresa,
					kontekst->spremnik->velicina, 
					kontekst->spremnik->razina );
}

//po prekidu, prije same obrade
inline void *arh_pohrani_okolinu_dretve ( void *_kontekst )
{
	return arh_sacuvaj_kontekst ( _kontekst );
}


//pretvara adresu jezgre u adresu za program
inline void *arh_jup(void *_spremnik, void *adr_u_jezgri)
{
	arh_spremnik_t *spremnik = _spremnik;

	return adr_u_jezgri - spremnik->odmak;
}

//pretvara adresu programa u adresu koja je dohvatljiva iz jezgre
inline void *arh_puj(void *_spremnik, void *adr_u_programu)
{
	arh_spremnik_t *spremnik = _spremnik;

	return adr_u_programu + spremnik->odmak;
}

//azurira segmente sa pocetnom adresom, velicinom i razinom
static void arh_azuriraj_opisnik_segmenta (int id, void *poc_adr, uint32 vel,
					   int razina)
{
	uint32 addr = (uint32) poc_adr;
	uint32 v = vel;

	gdt[id].base_addr0 =  addr & 0x0000ffff;
	gdt[id].base_addr1 = (addr & 0x00ff0000) >> 16;
	gdt[id].base_addr2 = (addr & 0xff000000) >> 24;

	if (vel < (1 << 20)) { //vel < 1 MB?
		v = vel - 1;
		gdt[id].G = 0; //granularnost 1 B
	}
	else { //granularnost 4 KB
		v = vel >> 12;
		if (vel & 0x0fff)
			v++;
		v--;
		gdt[id].G = 1;
	}
	gdt[id].segm_limit0 =  v & 0x0000ffff;
	gdt[id].segm_limit1 = (v & 0x000f0000) >> 16;

	gdt[id].DPL = razina;
}

/*
//testiranje
static void arh_ispisi_gdt_zapis(int id)
{
	gdt_t *g = &gdt[id];

	j_ispisi("%x %d %d %d %d %x %d %d %d %d %x %x %x\n",
		g->base_addr2,
		g->G,
		g->DB,
		g->L,
		g->AVL,
		g->segm_limit1,
		g->P,
		g->DPL,
		g->S,
		g->type,
		g->base_addr1,
		g->base_addr0,
		g->segm_limit0);

}
*/